import sys
from math import inf

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import QApplication, QTableWidgetItem, QWidget, QDialog, QMessageBox
from UI.ui_graph_visualizer import Ui_GraphVisualizer
import matplotlib.pyplot as plt
import networkx as nx
from matplotlib.patches import Rectangle
from matplotlib.patches import FancyArrowPatch, Circle
from Core import *
from initialization_dialog import InitializationDialog
import qdarktheme
class GraphVisualizer(QWidget, Ui_GraphVisualizer):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        # Connect the button to the calculate method
        self.calculate_button.clicked.connect(self.on_schedule)
        self.plan_button.clicked.connect(self.calculate_optimal_planning)
        self.init_button.clicked.connect(self.on_initialize)
        self.reservation = None
        self.forbidden_list = None
        self.collision_vector = None
        self.vector_list = None
        self.adjacency_list = None
        self.adjacency_matrix = None
        self.min_avg_weight = None
        self.best_cycle = None
        self.arc_info = []

    def set_collision_vector(self,collision_vector):
        self.collision_vector = collision_vector
    def calculate_graph(self):
        try:
            # Placeholder collision vector
            # self.collision_vector = np.array([1, 0, 1, 1, 0,1,0,1])
            if self.hyperoffset.isChecked():
            # Generate graph
                self.vector_list, self.adjacency_list = generate_adjacency_list(self.collision_vector, max_iterations=10,hyperoffset=True)
            else:
                self.vector_list, self.adjacency_list = generate_adjacency_list(self.collision_vector,
                                                                                max_iterations=10)

            self.visualize_graph(self.adjacency_list, self.vector_list)


        except Exception as e:
            self.result_text.append(f"Error: {e}")

    def calculate_optimal_planning(self):
        try:
            # 计算邻接矩阵和最小平均权重环
            self.adjacency_matrix = adjacency_list_to_matrix(self.adjacency_list, len(self.vector_list))
            self.min_avg_weight, self.best_cycle = find_minimum_average_cycle(self.adjacency_matrix)

            # 更新 UI 显示结果
            # self.result_text.append(f"\u2713 Generated {len(self.vector_list)} vectors.")
            # self.result_text.append(f"\u2713 Minimum Average Weight Cycle: {self.min_avg_weight:.4f}")
            # self.result_text.append("\u2713 Best Cycle Path:")

            self.result_text.append(f"\u2713 生成了 {len(self.vector_list)} 个向量.")
            self.result_text.append(f"\u2713 平均最小权为: {self.min_avg_weight:.4f}")
            self.result_text.append("\u2713 最佳调度方案为:")

            # 显示最优循环路径
            formatted_cycle = ""
            for i in range(len(self.best_cycle) - 1):
                start_vector = self.vector_list[self.best_cycle[i]]
                weight = self.adjacency_matrix[self.best_cycle[i]][self.best_cycle[i + 1]]
                formatted_cycle += f"{start_vector} ——{weight}——> "
            #formatted_cycle += f"{self.vector_list[self.best_cycle[-1]]} ……(cycle closed)"
            formatted_cycle += f"{self.vector_list[self.best_cycle[-1]]} ……(周期循环)"
            self.result_text.append(formatted_cycle)
            # self.update_adjacency_table(self.adjacency_matrix)
            for i in range(len(self.best_cycle) - 1):
                weight = inf
                u = -1
                v = -1
                index = -1
                for j, info in enumerate(self.arc_info):
                    if info[0] == self.best_cycle[i] and info[1] == self.best_cycle[i + 1]:
                        if u != info[0] or v != info[1]:
                            weight = inf
                            index = -1
                        if info[3]['weight'] < weight:
                            weight = info[3]['weight']
                            if index != -1:
                                self.arc_info[index][4] = False
                            index = j
                            info[4] = True
                            u = info[0]
                            v = info[1]
            print(self.arc_info)
            self.visualize_graph(self.adjacency_list, self.vector_list)
        except Exception as e:
            self.result_text.append(f"Error: {e}")


    def visualize_graph(self, adjacency_list, vector_list):
        # 使用 MultiDiGraph 以支持多重边
        graph = nx.MultiDiGraph()

        # 构建图
        for src, edges in adjacency_list.items():
            for dst, weight in edges:
                graph.add_edge(src, dst, weight=weight)

        # 定义布局
        pos = nx.circular_layout(graph)  # 设置种子确保布局一致
        min_x = min(coord[0] for coord in pos.values())
        max_x = max(coord[0] for coord in pos.values())
        min_y = min(coord[1] for coord in pos.values())
        max_y = max(coord[1] for coord in pos.values())

        for node in pos:
            if min_x!= max_x:
                pos[node][0] = (pos[node][0] - min_x) / (max_x - min_x)
            else:
                pos[node][0] = 0.5
            if min_y!= max_y:
                pos[node][1] = (pos[node][1] - min_y) / (max_y - min_y)
            else:
                pos[node][1] = 0.5
        print(pos)

        plt.figure(figsize=(5, 5))
        ax = plt.gca()

        # 初始化偏移字典，记录每对节点的标签偏移
        label_offsets = {}

        # 绘制边和标签
        for u, v, k, d in graph.edges(keys=True, data=True):
            is_loop = False
            if u == v:
                is_loop = True

            # 动态调整弧度，反向边弧度取负
            rad = 0.2 + k * 0.2
            if pos[u][0]<pos[v][0] and pos[u][1]<pos[v][1]:
                rad*= -1
            if pos[u][0]>pos[v][0] and pos[u][1]>pos[v][1]:
                rad*= -1
            color = 'black'
            for info in self.arc_info:
                if info is not None and info[0] == u and info[1] == v and info[2] == k and info[4] == True:
                    color = 'blue'
            # 绘制边
            if not is_loop:
                nx.draw_networkx_edges(
                    graph,
                    pos,
                    edgelist=[(u, v)],
                    connectionstyle=f"arc3,rad={rad}",
                    arrowstyle="->",
                    arrowsize=1,
                    edge_color=color

                )
                nx.draw_networkx_edges(
                    graph,
                    pos,
                    edgelist=[(u, v)],
                    connectionstyle=f"arc3,rad={rad}",
                    arrowstyle="->",
                    arrowsize=15,
                    min_source_margin=50,  # 增加源节点边缘的距离
                    min_target_margin=100,  # 增加目标节点边缘的距离
                    edge_color=color
                )
                if len(pos.items()) == 2:
                    mid_x, mid_y = bezier_midpoint(pos[u], pos[v], -rad*1.5)
                else:
                    mid_x, mid_y = bezier_midpoint(pos[u], pos[v], -rad)

                # 绘制权值标签
                plt.text(
                    mid_x,
                    mid_y,
                    f"{d['weight']}",
                    fontsize=12,
                    color="red",
                    ha="center",
                    va="center",
                    bbox=dict(
                        boxstyle="round",  # 边框样式，如'round', 'square'等
                        facecolor='white',  # 填充颜色
                        edgecolor='black',  # 边缘颜色
                        alpha=0.7,  # 透明度，0为完全透明，1为完全不透明
                        pad=0.2  # 文本与边框之间的间距
                    )
                )
            else:
                factor = 1
                # 自环：绘制圆
                center_x, center_y = pos[u]
                center_y += factor*0.005
                radius = rad / 5  # 圆的半径
                if (center_y + radius) > 1.0:
                    factor = -1
                loop_circle = Circle(
                    (center_x, center_y + factor*radius),
                    radius,
                    color=color,
                    fill=False,
                    # lw=1.5,
                )
                ax.add_patch(loop_circle)

                # 绘制自环箭头
                arrow_x = center_x + radius
                arrow_y = center_y + factor*radius
                arrow = FancyArrowPatch(
                    (arrow_x, arrow_y - 0.001),
                    (arrow_x, arrow_y + 0.001),
                    arrowstyle="->",
                    color=color,
                    mutation_scale=15,
                )
                ax.add_patch(arrow)
                mid_x = center_x
                mid_y = center_y + factor*2*radius
                # 绘制权值标签
                plt.text(
                    mid_x,
                    mid_y,
                    f"{d['weight']}",
                    fontsize=12,
                    color="red",
                    ha="center",
                    va="center",
                    bbox=dict(
                        boxstyle="round",  # 边框样式，如'round', 'square'等
                        facecolor='white',  # 填充颜色
                        edgecolor='black',  # 边缘颜色
                        alpha=0.7,  # 透明度，0为完全透明，1为完全不透明
                        pad=0.2  # 文本与边框之间的间距
                    )
                )

            # 初始化或更新标签偏移值
            if (u, v) not in label_offsets:
                label_offsets[(u, v)] = []
            label_offsets[(u, v)].append(k)




            self.arc_info.append([u, v, k, d, False])
            print(u, v, k, d, False)
        # 绘制节点矩形
        for node, (x, y) in pos.items():
            vector = vector_list[node]
            text = str(vector)
            width = len(text) * 0.015 # 根据内容长度动态调整宽度
            height = 0.04  # 固定高度

            # 绘制矩形
            rect = Rectangle(
                (x - width / 2, y - height / 2),
                width,
                height,
                color="skyblue",
                ec="black",
                lw=1.5,

            )
            ax.add_patch(rect)

            # 绘制节点标签
            plt.text(
                x,
                y,
                text,
                fontsize=8,
                ha="center",
                va="center",
            )
        # 假设 QLabel 对象为 self.graph_pixmap
        label_width = self.graph_pixmap.width()  # 获取 QLabel 的宽度
        label_height = self.graph_pixmap.height()  # 获取 QLabel 的高度

        # 绘制图形
        plt.tight_layout()
        plt.axis("off")  # 关闭坐标轴
        plt.subplots_adjust(left=0, right=1, top=1, bottom=0)  # 移除外边框

        # 保存图像
        plt.savefig("graph.png", format="png", dpi=300)  # 确保高质量保存
        plt.close()

        # 加载图像并调整大小以适应 QLabel
        pixmap = QPixmap("graph.png")
        scaled_pixmap = pixmap.scaled(label_width, label_height, aspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio)

        # 在 QLabel 上显示缩放后的图片
        self.graph_pixmap.setPixmap(scaled_pixmap)
    def on_initialize(self):
        """初始化预约表"""
        # 弹出自定义对话框获取用户输入
        dialog = InitializationDialog(self)
        if dialog.exec() == QDialog.DialogCode.Accepted:
            num_function_units = dialog.num_function_units.value()
            num_time_slots = dialog.num_time_slots.value()

            # 动态设置表格的行数和列数
            self.reservation_table.setRowCount(num_function_units)
            self.reservation_table.setColumnCount(num_time_slots)

            # 生成表头标签
            header_labels = [f'T{i + 1}' for i in range(num_time_slots)]
            self.reservation_table.setHorizontalHeaderLabels(header_labels)

            # 初始化表格中的每个单元格为未打勾状态
            for i in range(num_function_units):
                for j in range(num_time_slots):
                    item = QTableWidgetItem()
                    item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
                    item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
                    item.setCheckState(Qt.CheckState.Unchecked)
                    self.reservation_table.setItem(i, j, item)

            self.reservation_table.resizeColumnsToContents()
            self.result_text.append(
                #f"Reservation table initialized with {num_function_units} function units and {num_time_slots} time slots.")
                f"预约表初始化为 {num_function_units} 个功能段和 {num_time_slots} 个时间段.")
        else:
            #self.result_text.append("Initialization cancelled by user.")
            self.result_text.append("初始化取消.")

    def update_reservation(self):
        """从表格中读取用户输入的数据并更新 Scheduler 的 reservation 属性"""
        if self.reservation_table.rowCount() == 0:
            #self.result_text.append("No function units defined.")
            self.result_text.append("没有定义功能段.")
            return

        reservation_data = []
        for i in range(self.reservation_table.rowCount()):
            row_data = []
            for j in range(self.reservation_table.columnCount()):
                item = self.reservation_table.item(i, j)
                if item.checkState() == Qt.CheckState.Checked:
                    row_data.append(1)
                else:
                    row_data.append(0)
            reservation_data.append(row_data)

        self.reservation = np.array(reservation_data)
        #self.result_text.append("Reservation table updated from user input.")
        self.result_text.append("预约表根据用户输入更新.")

    def check_unassigned(self):
        """检查是否有未分配的功能段，以及第一个和最后一个时间段是否至少有一个功能段被分配"""
        unassigned_rows = []
        first_column_assigned = False
        last_column_assigned = False

        # 检查每一行
        for row in range(self.reservation_table.rowCount()):
            row_assigned = False
            for col in range(self.reservation_table.columnCount()):
                item = self.reservation_table.item(row, col)
                if item and item.checkState() == Qt.CheckState.Checked:
                    row_assigned = True
                    if col == 0:
                        first_column_assigned = True
                    if col == self.reservation_table.columnCount() - 1:
                        last_column_assigned = True
                    break
            if not row_assigned:
                unassigned_rows.append(row)

        # 检查最后一列是否有功能段被分配
        if not last_column_assigned:
            for row in range(self.reservation_table.rowCount()):
                item = self.reservation_table.item(row, self.reservation_table.columnCount() - 1)
                if item and item.checkState() == Qt.CheckState.Checked:
                    last_column_assigned = True
                    break

        return unassigned_rows, first_column_assigned, last_column_assigned

    def check_start_time_order(self):
        """检查功能段的起始时间段是否严格递增，不能早于或等于任何在其之前的功能段的起始时间段"""
        start_times = []  # 用于存储每个功能段的起始时间段

        # 获取每个功能段的起始时间段
        for row in range(self.reservation_table.rowCount()):
            start_time = None
            for col in range(self.reservation_table.columnCount()):
                item = self.reservation_table.item(row, col)
                if item and item.checkState() == Qt.CheckState.Checked:
                    start_time = col
                    break
            start_times.append(start_time)  # 如果该行没有打勾，则 start_time 为 None

        # 检查起始时间顺序
        time_order_violations = []
        for i in range(1, len(start_times)):
            if start_times[i] is not None:
                for j in range(i):
                    if start_times[j] is not None and start_times[i] <= start_times[j]:
                        time_order_violations.append((i, j))  # 记录违反顺序的功能段对 (当前行, 前面的行)
                        break

        return time_order_violations

    def show_unassigned_error(self, unassigned_rows, first_column_assigned, last_column_assigned,
                              time_order_violations):
        """弹出错误对话框，显示未分配的行和列以及起始时间顺序错误"""
        error_message = "以下功能段和时间段尚未进行时间分配：\n\n"

        if unassigned_rows:
            error_message += "以下功能段尚未分配时间段：\n"
            for row in unassigned_rows:
                error_message += f"  - 功能段 {row + 1}\n"

        if not first_column_assigned:
            error_message += "\n第一个时间段（T1）尚未分配任何功能段。\n"

        if not last_column_assigned:
            error_message += "\n最后一个时间段（Tn）尚未分配任何功能段。\n"

        if time_order_violations:
            error_message += "\n以下功能段的起始时间段违反了顺序规则：\n"
            for current_row, prev_row in time_order_violations:
                error_message += f"  - 功能段 {current_row + 1} 的起始时间段早于或等于功能段 {prev_row + 1} 的起始时间段。\n"

        # 弹出错误对话框
        if error_message != "以下功能段和时间段尚未进行时间分配：\n\n":
            msg_box = QMessageBox(self)
            msg_box.setIcon(QMessageBox.Icon.Warning)
            msg_box.setWindowTitle("调度错误")
            msg_box.setText(error_message)
            msg_box.exec()


    def on_schedule(self):
        """计算调度方案"""
        try:
            self.result_text.clear()
            self.arc_info = []
            self.update_reservation()

            # 检查是否有未分配的功能段，以及第一个和最后一个时间段是否至少有一个功能段被分配
            unassigned_rows, first_column_assigned, last_column_assigned = self.check_unassigned()

            # 检查功能段的起始时间段是否严格递增
            time_order_violations = self.check_start_time_order()

            if unassigned_rows or not first_column_assigned or not last_column_assigned or time_order_violations:
                # 如果有未分配的功能段、指定的时间段未分配或起始时间顺序错误，弹出错误对话框并阻止调度操作
                self.show_unassigned_error(unassigned_rows, first_column_assigned, last_column_assigned,
                                           time_order_violations)
                return

            if self.reservation is not None:
                # 生成禁止表并输出
                forbidden_list = generate_forbidden_list(self.reservation)
                #self.result_text.append(f"Forbidden List: {forbidden_list}")
                self.result_text.append(f"禁止表: {forbidden_list}")

                # 生成碰撞向量并输出
                collision_vector = generate_collision_vector(forbidden_list, self.reservation.shape[1])
                #self.result_text.append(f"Collision Vector: {collision_vector}")
                self.result_text.append(f"冲突向量: {collision_vector}")

                self.collision_vector = collision_vector

                self.calculate_graph()

        except Exception as e:
            #self.result_text.append(f"Error during scheduling: {str(e)}")
            self.result_text.append(f"调度错误: {str(e)}")
            print(f"Error during scheduling: {str(e)}")  # 打印详细错误信息

if __name__ == "__main__":
    app = QApplication(sys.argv)
    qdarktheme.setup_theme("light")
    window = GraphVisualizer()
    window.show()
    sys.exit(app.exec())
